﻿using Furion.Authorization;
using Furion.DataEncryption;
using System.Runtime.CompilerServices;
using Yitter.IdGenerator;

namespace GoodAdmin.System;

public class AuthService : IAuthService, ITransient
{
    private readonly ISysUserService _userService;
    private readonly IConfigService _configService;
    private readonly ISysRoleService _roleService;
    private readonly IRedisCacheManager _redisCacheManager;

    public AuthService(ISysUserService userService,IConfigService configService,ISysRoleService roleService,IRedisCacheManager redisCacheManager)
    {
        _userService = userService;
        _configService = configService;
        _roleService = roleService;
        _redisCacheManager = redisCacheManager;
    }

    /// <inheritdoc/>
    public dynamic GetCaptcha()
    {
        var captcha= CaptchaCodeGenerator.GenerateImage(CaptchaType.DEFAULT,4);
        var CodeId=SaveCaptcha(captcha.Code);
        return new { CodeId, Img = captcha.Base64Str,captcha.Code };
    }
    
    /// <inheritdoc/>
    public async Task<dynamic> GetLoginConfig()
    {
        var isWaterMark= (await _configService.GetConfigInfo(CategoryConst.SYS_CONFIG, ConfigConst.SYS_WARTERMARK)).ConfigValue.ToBoolean();
        var isCaptcha= (await _configService.GetConfigInfo(CategoryConst.SYS_CONFIG, ConfigConst.SYS_CAPTCHA)).ConfigValue.ToBoolean();
        return new { isWaterMark, isCaptcha };
    }

    /// <inheritdoc/>
    public async Task<LoginOutput> Login([Required]LoginInput input)
    {
        var captcha = await _configService.GetConfigInfo(CategoryConst.SYS_CONFIG, ConfigConst.SYS_CAPTCHA);
        //是否有启用验证码
        if (captcha.ConfigValue.ToBoolean())
        {
            //校验验证码
            ValidCaptcha(input.CaptchaCode, input.CaptchaCodeId);
        }
        var user = await _userService.GetUserByAccount(input.Account);

        await CheckUser(user,input.Password);
        //登录验证通过后，获取用户角色及数据范围        
        var userRole = await _roleService.GetUserRoleList(user.Id);
        //验证是否是超级管理员
        var isSuperAdmin = IsSuperAdmin(user, userRole);
        // 生成Token令牌
        var accessToken = JWTEncryption.Encrypt(new Dictionary<string, object>
        {
            {ClaimConst.UserId, user.Id},
            {ClaimConst.Account, user.Account},
            {ClaimConst.Name, user.Name},
            {ClaimConst.SuperAdmin, isSuperAdmin },
            {ClaimConst.OrgId, user.OrgId},
        });
        //过期时间单位分钟（一般 refresh_token 的有效时间 > 2 * access_token 的有效时间）
        var expireTime = App.GetOptions<JWTSettingsOptions>().ExpiredTime.ToInt();
        // 生成刷新Token令牌
        var refreshToken = JWTEncryption.GenerateRefreshToken(accessToken, expireTime * 2);

        var tokenInfo = new TokenInfo
        {
            Token = accessToken,
            TokenExpireTime = DateTime.Now.AddMinutes(expireTime),
            Expire = expireTime,
            LoginTime=DateTime.Now
        };
        await WriteTokenToCache(user.Id, tokenInfo);
        // 设置响应报文头
        App.HttpContext.SetTokensOfResponseHeaders(accessToken, refreshToken);
        //返回登录信息
        return new LoginOutput { AccessToken=accessToken, Account = input.Account, Name =user.Name, };
    }

    /// <inheritdoc/>
    public void LoginOut(string token)
    {
        if (UserManager.UserId == 0)
        {
            throw Oops.Bah(AuthErrorCode.A007);
        }
        //退出时移除token
        RemoveToken(UserManager.UserId,token);   
        
    }


    /// <summary>
    /// 验证用户是否是超级管理员
    /// </summary>
    /// <returns></returns>
    private static bool IsSuperAdmin(SysUser user,List<SysRole> roleList )
    {
        if (user.Account == RoleConst.SUPERADMIN) return true;
        //判断用户角色列表是否有超级管理员        
        return roleList.Any(r=>r.Code==RoleConst.SUPERADMIN);
    }

    /// <summary>
    /// 登录密码校验
    /// </summary>
    /// <param name="user"></param>
    /// <param name="password"></param>
    /// <returns></returns>
    private async Task CheckUser(SysUser user,string password)
    {
        _ = user ?? throw Oops.Bah(UserErrorCode.U009);

        if (user.Status == CommonConst.STATUS_DISABLE) throw Oops.Bah(UserErrorCode.U006);
        var LockTime = _redisCacheManager.HashGet<string>(CacheConst.LockedUser, user.Id.ToString())[0];
        //如果在锁定名单中
        if (LockTime != null)
        {
            //执行策略
            var strategyConfig = await _configService.GetConfigInfo(CategoryConst.SYS_CONFIG, ConfigConst.SYS_PASSWORD_WRONG_STRATEGY);
            //锁定用户
            if (strategyConfig.ConfigValue == CommonConst.LOCK_USER)
            {
                throw Oops.Bah(AuthErrorCode.A003);
            }
            //延迟登录
            else
            {
                //获取错误延迟登录时间
                var delayTime = await _configService.GetConfigInfo(CategoryConst.SYS_CONFIG, ConfigConst.SYS_LOGIN_DELAY_TIME);
                
                //判断解锁时间
                //错误锁定时间-已锁定时间（当前时间-记录时间）
                var time = delayTime.ConfigValue.ToDouble()-(DateTime.Now- LockTime.ToDateTime()).TotalMinutes;
                if (time > 0)
                {
                    var alter= time <1 ? 1 : time.ToInt();
                    throw Oops.Bah(AuthErrorCode.A004, alter);
                }
            }
        }
        var decryptPassword = CryptogramUtil.Sm2Decrypt(user.Password);
        if (password != decryptPassword)
        {
            await UpdateErrorCache(user.Id);
            throw Oops.Bah(AuthErrorCode.A002);            
        }
        RemoveWrongInfo(user.Id);
    }

    /// <summary>
    /// 密码输入错误后进行处理
    /// </summary>
    /// <param name="userId"></param>
    /// <returns></returns>
    private async Task UpdateErrorCache(long userId)
    {
        //获取密码允许错误次数
        var passwordConfig = await _configService.GetConfigInfo(CategoryConst.SYS_CONFIG, ConfigConst.SYS_PASSWORD_WRONG_NUMBER);
        var configNum= passwordConfig.ConfigValue.ToInt();
        //如果次数为非正数，表示不限制次数
        if (configNum <= 0) return;
        //获取用户登录错误次数
        var userNumber = _redisCacheManager.HashGet<int>(CacheConst.UserLoginWrongNumber, userId.ToString())[0]; ;
        //校验次数
        //如果已输入错误次数+当前这一次错误，大于等于允许次数，那么计入锁定信息缓存中
        if (userNumber + 1 >= configNum)
        {
            _redisCacheManager.HashSet(CacheConst.LockedUser, userId.ToString(), DateTime.Now.ToString());                   
        }
        //否则输入次数+1
        else
        {
            _redisCacheManager.HashIncrement<int>(CacheConst.UserLoginWrongNumber, userId.ToString(), 1);
        }
    }

    /// <summary>
    /// 成功登录后，清除密码错误信息缓存
    /// </summary>
    /// <param name="userId"></param>
    private void RemoveWrongInfo(long userId)
    {
        _redisCacheManager.HashDel<string>(CacheConst.LockedUser, userId.ToString());
        _redisCacheManager.HashDel<int>(CacheConst.UserLoginWrongNumber, userId.ToString());
    }

    /// <summary>
    /// 存储验证码
    /// </summary>
    /// <param name="code"></param>
    /// <param name="expire"></param>
    private string SaveCaptcha(string code, int expire = 5)
    {
        var codeId = YitIdHelper.NextId().ToString();
        _redisCacheManager.Set(CacheConst.Captcha + codeId, code, TimeSpan.FromMinutes(expire));
        return codeId;
    }

    /// <summary>
    /// 验证验证码
    /// </summary>
    /// <param name="validCode"></param>
    /// <param name="validCodeId"></param>
    /// <param name="IsIgnorCase">是否忽略大小写，默认为true</param>
    private void ValidCaptcha(string validCode, string validCodeId,bool IsIgnorCase=true)
    {
        string key = CacheConst.Captcha + validCodeId;
        var code = _redisCacheManager.Get<string>(key);
        _redisCacheManager.Remove(key);
        if (code == null)
        {
            throw Oops.Bah(AuthErrorCode.A006);
        }
        if (IsIgnorCase)
        {
            if (!string.Equals(code, validCode, StringComparison.OrdinalIgnoreCase))
                throw Oops.Bah(AuthErrorCode.A005);
        }
        else
        {
            if (code != validCode)
                throw Oops.Bah(AuthErrorCode.A005);
        }
    }
    
    /// <summary>
    /// token信息保存到redis中
    /// </summary>
    /// <param name="userId"></param>
    /// <param name="tokenInfo"></param>
    private async Task WriteTokenToCache(long userId,TokenInfo tokenInfo)
    {        
        var tokenList = GetTokenInfos(userId);
        if (tokenList != null)
        {
            // 是否单点登录
            SimpleConfig isSingleLogin = await _configService.GetConfigInfo(CategoryConst.SYS_CONFIG, ConfigConst.SINGLE_LOGIN);
            if (isSingleLogin!=null && isSingleLogin.ConfigValue.ToBoolean())
            {
                // 根据登录设备类型判断，这些token做下线处理
                var otherTokens = tokenList.Where(it => it.LoginDevice == tokenInfo.LoginDevice).ToList();
                
                // token列表中移除相同设备类型token
                tokenList=tokenList.Where(it=>it.LoginDevice != tokenInfo.LoginDevice).ToList();
            }
            tokenList.Add(tokenInfo);
        }
        else
        {
            tokenList = new List<TokenInfo> { tokenInfo };
        }
        var key = CacheConst.UserToken + userId;
        _redisCacheManager.Set(key, tokenList);
    }
    /// <summary>
    /// 退出登录时移除token
    /// </summary>
    /// <param name="userId"></param>
    private void RemoveToken(long userId,string token)
    {        
        var tokenList = GetTokenInfos(userId);
        if (tokenList == null) return;
        var tokenInfo = tokenList.FirstOrDefault(it => it.Token == token);
        if(tokenInfo!=null) tokenList.Remove(tokenInfo);
        var key = CacheConst.UserToken + userId;

        //如果移除当前token后没有数据，那么直接删除redis中记录
        if (tokenList.Count == 0)
        {
            _redisCacheManager.Remove(key);
        }
        else//否则重新保存
        {
            _redisCacheManager.Set(key, tokenList);
        }
    }

    /// <summary>
    /// 获取用户token
    /// </summary>
    /// <param name="userId"></param>
    /// <returns></returns>
    private List<TokenInfo> GetTokenInfos(long userId)
    {
        var key = CacheConst.UserToken + userId;
        var tokens = _redisCacheManager.Get<List<TokenInfo>>(key);
        if (tokens != null)
        {
            //筛选出还没有过期的
            tokens = tokens.Where(it => it.TokenExpireTime > DateTime.Now).ToList();
        }
        return tokens;
    }
}